/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/** \file
 * \ingroup freestyle
 * \brief Class to define the representation of a stroke (for display purpose)
 */

#include "Stroke.h"
#include "StrokeIterators.h"
#include "StrokeAdvancedIterators.h"
#include "StrokeRenderer.h"
#include "StrokeRep.h"

#include "BKE_global.h"

using namespace std;

namespace Freestyle {

//
// STROKE VERTEX REP
/////////////////////////////////////

StrokeVertexRep::StrokeVertexRep(const StrokeVertexRep &iBrother)
{
  _point2d = iBrother._point2d;
  _texCoord = iBrother._texCoord;
  _texCoord_w_tips = iBrother._texCoord_w_tips;
  _color = iBrother._color;
  _alpha = iBrother._alpha;
}

//
// STRIP
/////////////////////////////////////

Strip::Strip(const vector<StrokeVertex *> &iStrokeVertices,
             bool hasTex,
             bool beginTip,
             bool endTip,
             float texStep)
{
  createStrip(iStrokeVertices);

  setVertexColor(iStrokeVertices);

  if (hasTex) {
    // We compute both kinds of coordinates to use different kinds of textures
    computeTexCoord(iStrokeVertices, texStep);
    computeTexCoordWithTips(iStrokeVertices, beginTip, endTip, texStep);
  }
}

Strip::Strip(const Strip &iBrother)
{
  if (!iBrother._vertices.empty()) {
    for (vertex_container::const_iterator v = iBrother._vertices.begin(),
                                          vend = iBrother._vertices.end();
         v != vend;
         ++v) {
      _vertices.push_back(new StrokeVertexRep(**v));
    }
  }
  _averageThickness = iBrother._averageThickness;
}

Strip::~Strip()
{
  if (!_vertices.empty()) {
    for (vertex_container::iterator v = _vertices.begin(), vend = _vertices.end(); v != vend;
         ++v) {
      delete (*v);
    }
    _vertices.clear();
  }
}

//////////////////////////
// Strip creation
//////////////////////////

#define EPS_SINGULARITY_RENDERER 0.05
#define ZERO 0.00001
#define MAX_RATIO_LENGTH_SINGU 2
#define HUGE_COORD 1.0e4

static bool notValid(Vec2r p)
{
  return (p[0] != p[0]) || (p[1] != p[1]) || (fabs(p[0]) > HUGE_COORD) ||
         (fabs(p[1]) > HUGE_COORD) || (p[0] < -HUGE_COORD) || (p[1] < -HUGE_COORD);
}

#if 0
static real crossP(const Vec2r &A, const Vec2r &B)
{
  return A[0] * B[1] - A[1] * B[0];
}
#endif

void Strip::createStrip(const vector<StrokeVertex *> &iStrokeVertices)
{
  // computeParameterization();
  if (iStrokeVertices.size() < 2) {
    if (G.debug & G_DEBUG_FREESTYLE) {
      cout << "Warning: strip has less than 2 vertices" << endl;
    }
    return;
  }
  _vertices.reserve(2 * iStrokeVertices.size());
  if (!_vertices.empty()) {
    for (vertex_container::iterator v = _vertices.begin(), vend = _vertices.end(); v != vend;
         ++v) {
      delete (*v);
    }
    _vertices.clear();
  }
  _averageThickness = 0.0;

  vector<StrokeVertex *>::const_iterator v, vend, v2, vPrev;
  StrokeVertex *sv, *sv2, *svPrev;
  int orientationErrors = 0;

  // special case of first vertex
  v2 = v = iStrokeVertices.begin();
  ++v2;
  sv = *v;
  vPrev = v;  // in case the stroke has only 2 vertices;
  sv2 = *v2;
  Vec2r dir(sv2->getPoint() - sv->getPoint());
  Vec2r orthDir(-dir[1], dir[0]);
  if (orthDir.norm() > ZERO) {
    orthDir.normalize();
  }
  Vec2r stripDir(orthDir);
  // check whether the orientation was user defined
  if (sv->attribute().isAttributeAvailableVec2f("orientation")) {
    Vec2r userDir = sv->attribute().getAttributeVec2f("orientation");
    if (userDir.norm() > 1e-6) {
      userDir.normalize();
      real dp = userDir * orthDir;
      if (dp < 0) {
        userDir = userDir * (-1.0f);
      }
      stripDir = userDir;
    }
    else {
      ++orientationErrors;
    }
  }
  const float *thickness = sv->attribute().getThickness();
  _vertices.push_back(new StrokeVertexRep(sv->getPoint() + thickness[1] * stripDir));
  _vertices.push_back(new StrokeVertexRep(sv->getPoint() - thickness[0] * stripDir));

#if 0
  Vec2r userDir = _stroke->getBeginningOrientation();
  if (userDir != Vec2r(0, 0)) {
    userDir.normalize();
    real o1 = (orthDir * userDir);
    real o2 = crossP(orthDir, userDir);
    real orientation = o1 * o2;
    if (orientation > 0) {
      // then the vertex to move is v0
      if (o1 > 0) {
        _vertex[0] = _vertex[1] + userDir;
      }
      else {
        _vertex[0] = _vertex[1] - userDir;
      }
    }
    if (orientation < 0) {
      // then we must move v1
      if (o1 < 0) {
        _vertex[1] = _vertex[0] + userDir;
      }
      else {
        _vertex[1] = _vertex[0] - userDir;
      }
    }
  }
#endif

  int i = 2;  // 2 because we have already processed the first vertex

  for (vend = iStrokeVertices.end(), ++v, ++v2; v2 != vend; vPrev = v++, ++v2) {
    sv = (*v);
    sv2 = (*v2);
    svPrev = (*vPrev);
    Vec2r p(sv->getPoint()), p2(sv2->getPoint()), pPrev(svPrev->getPoint());

    // direction and orthogonal vector to the next segment
    Vec2r dir(p2 - p);
    float dirNorm = dir.norm();
    dir.normalize();
    Vec2r orthDir(-dir[1], dir[0]);
    Vec2r stripDir = orthDir;
    if (sv->attribute().isAttributeAvailableVec2f("orientation")) {
      Vec2r userDir = sv->attribute().getAttributeVec2f("orientation");
      if (userDir.norm() > 1e-6) {
        userDir.normalize();
        real dp = userDir * orthDir;
        if (dp < 0) {
          userDir = userDir * (-1.0f);
        }
        stripDir = userDir;
      }
      else {
        ++orientationErrors;
      }
    }

    // direction and orthogonal vector to the previous segment
    Vec2r dirPrev(p - pPrev);
    float dirPrevNorm = dirPrev.norm();
    dirPrev.normalize();
    Vec2r orthDirPrev(-dirPrev[1], dirPrev[0]);
    Vec2r stripDirPrev = orthDirPrev;
    if (svPrev->attribute().isAttributeAvailableVec2f("orientation")) {
      Vec2r userDir = svPrev->attribute().getAttributeVec2f("orientation");
      if (userDir.norm() > 1e-6) {
        userDir.normalize();
        real dp = userDir * orthDir;
        if (dp < 0) {
          userDir = userDir * (-1.0f);
        }
        stripDirPrev = userDir;
      }
      else {
        ++orientationErrors;
      }
    }

    const float *thickness = sv->attribute().getThickness();
    _averageThickness += thickness[0] + thickness[1];
    Vec2r pInter;
    int interResult;

    interResult = GeomUtils::intersect2dLine2dLine(Vec2r(pPrev + thickness[1] * stripDirPrev),
                                                   Vec2r(p + thickness[1] * stripDirPrev),
                                                   Vec2r(p + thickness[1] * stripDir),
                                                   Vec2r(p2 + thickness[1] * stripDir),
                                                   pInter);
    if (interResult == GeomUtils::DO_INTERSECT) {
      _vertices.push_back(new StrokeVertexRep(pInter));
    }
    else {
      _vertices.push_back(new StrokeVertexRep(p + thickness[1] * stripDir));
    }
    ++i;

    interResult = GeomUtils::intersect2dLine2dLine(Vec2r(pPrev - thickness[0] * stripDirPrev),
                                                   Vec2r(p - thickness[0] * stripDirPrev),
                                                   Vec2r(p - thickness[0] * stripDir),
                                                   Vec2r(p2 - thickness[0] * stripDir),
                                                   pInter);
    if (interResult == GeomUtils::DO_INTERSECT) {
      _vertices.push_back(new StrokeVertexRep(pInter));
    }
    else {
      _vertices.push_back(new StrokeVertexRep(p - thickness[0] * stripDir));
    }
    ++i;

    // if the angle is obtuse, we simply average the directions to avoid the singularity
    stripDir = stripDir + stripDirPrev;
    if ((dirNorm < ZERO) || (dirPrevNorm < ZERO) || (stripDir.norm() < ZERO)) {
      stripDir[0] = 0;
      stripDir[1] = 0;
    }
    else {
      stripDir.normalize();
    }

    Vec2r vec_tmp(_vertices[i - 2]->point2d() - p);
    if ((vec_tmp.norm() > thickness[1] * MAX_RATIO_LENGTH_SINGU) || (dirNorm < ZERO) ||
        (dirPrevNorm < ZERO) || notValid(_vertices[i - 2]->point2d()) ||
        (fabs(stripDir * dir) < EPS_SINGULARITY_RENDERER)) {
      _vertices[i - 2]->setPoint2d(p + thickness[1] * stripDir);
    }

    vec_tmp = _vertices[i - 1]->point2d() - p;
    if ((vec_tmp.norm() > thickness[0] * MAX_RATIO_LENGTH_SINGU) || (dirNorm < ZERO) ||
        (dirPrevNorm < ZERO) || notValid(_vertices[i - 1]->point2d()) ||
        (fabs(stripDir * dir) < EPS_SINGULARITY_RENDERER)) {
      _vertices[i - 1]->setPoint2d(p - thickness[0] * stripDir);
    }
  }  // end of for

  // special case of last vertex
  sv = *v;
  sv2 = *vPrev;
  dir = Vec2r(sv->getPoint() - sv2->getPoint());
  orthDir = Vec2r(-dir[1], dir[0]);
  if (orthDir.norm() > ZERO) {
    orthDir.normalize();
  }
  Vec2r stripDirLast(orthDir);
  // check whether the orientation was user defined
  if (sv->attribute().isAttributeAvailableVec2f("orientation")) {
    Vec2r userDir = sv->attribute().getAttributeVec2f("orientation");
    if (userDir.norm() > 1e-6) {
      userDir.normalize();
      real dp = userDir * orthDir;
      if (dp < 0) {
        userDir = userDir * (-1.0f);
      }
      stripDirLast = userDir;
    }
    else {
      ++orientationErrors;
    }
  }
  const float *thicknessLast = sv->attribute().getThickness();
  _vertices.push_back(new StrokeVertexRep(sv->getPoint() + thicknessLast[1] * stripDirLast));
  ++i;
  _vertices.push_back(new StrokeVertexRep(sv->getPoint() - thicknessLast[0] * stripDirLast));
  ++i;

#if 0
  int n = i - 1;
  // check whether the orientation of the extremity was user defined
  userDir = _stroke->getEndingOrientation();
  if (userDir != Vec2r(0, 0)) {
    userDir.normalize();
    real o1 = (orthDir * userDir);
    real o2 = crossP(orthDir, userDir);
    real orientation = o1 * o2;
    if (orientation > 0) {
      // then the vertex to move is vn
      if (o1 < 0) {
        _vertex[n] = _vertex[n - 1] + userDir;
      }
      else {
        _vertex[n] = _vertex[n - 1] - userDir;
      }
    }
    if (orientation < 0) {
      // then we must move vn-1
      if (o1 > 0) {
        _vertex[n - 1] = _vertex[n] + userDir;
      }
      else {
        _vertex[n - 1] = _vertex[n] - userDir;
      }
    }
  }
#endif

  _averageThickness /= float(iStrokeVertices.size() - 2);
  // I did not use the first and last vertex for the average
  if (iStrokeVertices.size() < 3) {
    _averageThickness = 0.5 * (thicknessLast[1] + thicknessLast[0] + thickness[0] + thickness[1]);
  }

  if (orientationErrors > 0) {
    if (G.debug & G_DEBUG_FREESTYLE) {
      cout << "Warning: " << orientationErrors
           << " invalid zero-length orientation vector(s) found.\n";
    }
  }

  if (i != 2 * (int)iStrokeVertices.size()) {
    if (G.debug & G_DEBUG_FREESTYLE) {
      cout << "Warning: problem with stripe size\n";
    }
  }

  cleanUpSingularities(iStrokeVertices);
}

// CLEAN UP
/////////////////////////

void Strip::cleanUpSingularities(const vector<StrokeVertex *> &iStrokeVertices)
{
  int k;
  int sizeStrip = _vertices.size();

  for (k = 0; k < sizeStrip; k++) {
    if (notValid(_vertices[k]->point2d())) {
      if (G.debug & G_DEBUG_FREESTYLE) {
        cout << "Warning: strip vertex " << k << " non valid" << endl;
      }
      return;
    }
  }

  // return;
  if (iStrokeVertices.size() < 2) {
    return;
  }
  int i = 0, j;
  vector<StrokeVertex *>::const_iterator v, vend, v2;
  StrokeVertex *sv, *sv2;

  bool singu1 = false, singu2 = false;
  int timeSinceSingu1 = 0, timeSinceSingu2 = 0;

  // special case of first vertex
  v = iStrokeVertices.begin();
  for (vend = iStrokeVertices.end(); v != vend; v++) {
    v2 = v;
    ++v2;
    if (v2 == vend) {
      break;
    }
    sv = (*v);
    sv2 = (*v2);
    Vec2r p(sv->getPoint()), p2(sv2->getPoint());

    Vec2r dir(p2 - p);
    if (dir.norm() > ZERO) {
      dir.normalize();
    }
    Vec2r dir1, dir2;
    dir1 = _vertices[2 * i + 2]->point2d() - _vertices[2 * i]->point2d();
    dir2 = _vertices[2 * i + 3]->point2d() - _vertices[2 * i + 1]->point2d();

    if ((dir1 * dir) < -ZERO) {
      singu1 = true;
      timeSinceSingu1++;
    }
    else {
      if (singu1) {
        int toto = i - timeSinceSingu1;
        if (toto < 0) {
          cerr << "Stephane dit \"Toto\"" << endl;
        }
        // traverse all the vertices of the singularity and average them
        Vec2r avP(0.0, 0.0);
        for (j = i - timeSinceSingu1; j <= i; j++) {
          avP = Vec2r(avP + _vertices[2 * j]->point2d());
        }
        avP = Vec2r(1.0 / float(timeSinceSingu1 + 1) * avP);
        for (j = i - timeSinceSingu1; j <= i; j++) {
          _vertices[2 * j]->setPoint2d(avP);
        }
        //_vertex[2 * j] = _vertex[2 * i];
        singu1 = false;
        timeSinceSingu1 = 0;
      }
    }
    if ((dir2 * dir) < -ZERO) {
      singu2 = true;
      timeSinceSingu2++;
    }
    else {
      if (singu2) {
        int toto = i - timeSinceSingu2;
        if (toto < 0) {
          cerr << "Stephane dit \"Toto\"" << endl;
        }
        // traverse all the vertices of the singularity and average them
        Vec2r avP(0.0, 0.0);
        for (j = i - timeSinceSingu2; j <= i; j++) {
          avP = Vec2r(avP + _vertices[2 * j + 1]->point2d());
        }
        avP = Vec2r(1.0 / float(timeSinceSingu2 + 1) * avP);
        for (j = i - timeSinceSingu2; j <= i; j++) {
          _vertices[2 * j + 1]->setPoint2d(avP);
        }
        //_vertex[2 * j + 1] = _vertex[2 * i + 1];
        singu2 = false;
        timeSinceSingu2 = 0;
      }
    }
    i++;
  }

  if (singu1) {
    // traverse all the vertices of the singularity and average them
    Vec2r avP(0.0, 0.0);
    for (j = i - timeSinceSingu1; j < i; j++) {
      avP = Vec2r(avP + _vertices[2 * j]->point2d());
    }
    avP = Vec2r(1.0 / float(timeSinceSingu1) * avP);
    for (j = i - timeSinceSingu1; j < i; j++) {
      _vertices[2 * j]->setPoint2d(avP);
    }
  }
  if (singu2) {
    // traverse all the vertices of the singularity and average them
    Vec2r avP(0.0, 0.0);
    for (j = i - timeSinceSingu2; j < i; j++) {
      avP = Vec2r(avP + _vertices[2 * j + 1]->point2d());
    }
    avP = Vec2r(1.0 / float(timeSinceSingu2) * avP);
    for (j = i - timeSinceSingu2; j < i; j++) {
      _vertices[2 * j + 1]->setPoint2d(avP);
    }
  }

  for (k = 0; k < sizeStrip; k++) {
    if (notValid(_vertices[k]->point2d())) {
      if (G.debug & G_DEBUG_FREESTYLE) {
        cout << "Warning: strip vertex " << k << " non valid after cleanup" << endl;
      }
      return;
    }
  }
}

// Vertex color (RGBA)
////////////////////////////////

void Strip::setVertexColor(const vector<StrokeVertex *> &iStrokeVertices)
{
  vector<StrokeVertex *>::const_iterator v, vend;
  StrokeVertex *sv;
  int i = 0;
  for (v = iStrokeVertices.begin(), vend = iStrokeVertices.end(); v != vend; v++) {
    sv = (*v);
    _vertices[i]->setColor(Vec3r(sv->attribute().getColorRGB()));
    _vertices[i]->setAlpha(sv->attribute().getAlpha());
    i++;
    _vertices[i]->setColor(Vec3r(sv->attribute().getColorRGB()));
    _vertices[i]->setAlpha(sv->attribute().getAlpha());
    i++;
#if 0
    cerr << "col=(" << sv->attribute().getColor()[0] << ", " << sv->attribute().getColor()[1]
         << ", " << sv->attribute().getColor()[2] << ")" << endl;
#endif
  }
}

// Texture coordinates
////////////////////////////////

void Strip::computeTexCoord(const vector<StrokeVertex *> &iStrokeVertices, float texStep)
{
  vector<StrokeVertex *>::const_iterator v, vend;
  StrokeVertex *sv;
  int i = 0;
  for (v = iStrokeVertices.begin(), vend = iStrokeVertices.end(); v != vend; v++) {
    sv = (*v);
    _vertices[i]->setTexCoord(
        Vec2r((real)(sv->curvilinearAbscissa() / (_averageThickness * texStep)), 0));
    i++;
    _vertices[i]->setTexCoord(
        Vec2r((real)(sv->curvilinearAbscissa() / (_averageThickness * texStep)), -1));
    i++;
  }
}

void Strip::computeTexCoordWithTips(const vector<StrokeVertex *> &iStrokeVertices,
                                    bool tipBegin,
                                    bool tipEnd,
                                    float texStep)
{
  vector<StrokeVertex *>::const_iterator v, vend;
  StrokeVertex *sv = NULL;
  StrokeVertexRep *tvRep[2] = {NULL};

  float l, fact, t;
  float u = 0, uPrev = 0;
  int tiles;
  int i = 0;
  float spacedThickness = _averageThickness * texStep;

  v = iStrokeVertices.begin();
  vend = iStrokeVertices.end();
  l = (*v)->strokeLength() / spacedThickness;
  tiles = int(l + 0.5);  // round to the nearest
  fact = (float(tiles) + 0.5) / l;

#if 0
  cerr << "l=" << l << "  tiles=" << tiles << "    _averageThicnkess=" << _averageThickness
       << "    strokeLength=" << (*v)->strokeLength() << endl;
#endif

  vector<StrokeVertexRep *>::iterator currentSV = _vertices.begin();
  StrokeVertexRep *svRep;
  if (tipBegin) {
    for (; v != vend; v++) {
      sv = (*v);
      svRep = *currentSV;
      u = sv->curvilinearAbscissa() / spacedThickness * fact;
      if (u > 0.25) {
        break;
      }

      svRep->setTexCoord(Vec2r((real)u, -0.5), true);
      i++;
      ++currentSV;

      svRep = *currentSV;
      svRep->setTexCoord(Vec2r((real)u, -1), true);
      i++;
      ++currentSV;
      uPrev = u;
    }

    if (v != vend && i >= 2) {
      // first transition vertex
      if (fabs(u - uPrev) > ZERO) {
        t = (0.25 - uPrev) / (u - uPrev);
      }
      else {
        t = 0;
      }
      for (int k = 0; k < 2; k++) {
        tvRep[k] = new StrokeVertexRep((1 - t) * _vertices[i - 2]->point2d() +
                                       t * _vertices[i]->point2d());
        tvRep[k]->setTexCoord((1 - t) * _vertices[i - 2]->texCoord() +
                              t * _vertices[i]->texCoord());
        // v coord is -0.5 for tvRep[0], -1.0 for tvRep[1]
        tvRep[k]->setTexCoord(Vec2r(0.25, -0.5 * (k + 1)), true);
        tvRep[k]->setColor((1 - t) * _vertices[i - 2]->color() +
                           t * Vec3r(sv->attribute().getColorRGB()));
        tvRep[k]->setAlpha((1 - t) * _vertices[i - 2]->alpha() + t * sv->attribute().getAlpha());
        i++;
      }
      for (int k = 0; k < 2; k++) {
        currentSV = _vertices.insert(currentSV, tvRep[k]);
        ++currentSV;
      }

      // copy the vertices with different texture coordinates
      for (int k = 0; k < 2; k++) {
        tvRep[k] = new StrokeVertexRep(*(_vertices[i - 2]));
        // v coord is 0.0 for tvRep[0], -0.5 for tvRep[1]
        tvRep[k]->setTexCoord(Vec2r(0.0, -0.5 * k), true);
        i++;
      }
      for (int k = 0; k < 2; k++) {
        currentSV = _vertices.insert(currentSV, tvRep[k]);
        ++currentSV;
      }
    }
  }
  uPrev = 0;

  // body of the stroke
  for (; v != vend; v++) {
    sv = (*v);
    svRep = *currentSV;
    u = sv->curvilinearAbscissa() / spacedThickness * fact - 0.25;
    if (u > tiles) {
      break;
    }

    svRep->setTexCoord(Vec2r((real)u, 0), true);
    i++;
    ++currentSV;

    svRep = *currentSV;
    svRep->setTexCoord(Vec2r((real)u, -0.5), true);
    i++;
    ++currentSV;

    uPrev = u;
  }

  if (tipEnd) {
    if (v != vend && i >= 2) {
      // second transition vertex
      if (fabs(u - uPrev) > ZERO) {
        t = (float(tiles) - uPrev) / (u - uPrev);
      }
      else {
        t = 0;
      }
      for (int k = 0; k < 2; k++) {
        tvRep[k] = new StrokeVertexRep((1 - t) * _vertices[i - 2]->point2d() +
                                       t * _vertices[i]->point2d());
        tvRep[k]->setTexCoord((1 - t) * _vertices[i - 2]->texCoord() +
                              t * _vertices[i]->texCoord());
        // v coord is 0.0 for tvRep[0], -0.5 for tvRep[1]
        tvRep[k]->setTexCoord(Vec2r((real)tiles, -0.5 * k), true);
        tvRep[k]->setColor((1 - t) * _vertices[i - 2]->color() +
                           t * Vec3r(sv->attribute().getColorRGB()));
        tvRep[k]->setAlpha((1 - t) * _vertices[i - 2]->alpha() + t * sv->attribute().getAlpha());
        i++;
      }
      for (int k = 0; k < 2; k++) {
        currentSV = _vertices.insert(currentSV, tvRep[k]);
        ++currentSV;
      }

      // copy the vertices with different texture coordinates
      for (int k = 0; k < 2; k++) {
        tvRep[k] = new StrokeVertexRep(*(_vertices[i - 2]));
        // v coord is -0.5 for tvRep[0], -1.0 for tvRep[1]
        tvRep[k]->setTexCoord(Vec2r(0.75, -0.5 * (k + 1)), true);
        i++;
      }
      for (int k = 0; k < 2; k++) {
        currentSV = _vertices.insert(currentSV, tvRep[k]);
        ++currentSV;
      }
    }

    // end tip
    for (; v != vend; v++) {
      sv = (*v);
      svRep = *currentSV;
      u = 0.75 + sv->curvilinearAbscissa() / spacedThickness * fact - float(tiles) - 0.25;

      svRep->setTexCoord(Vec2r((real)u, -0.5), true);
      i++;
      ++currentSV;

      svRep = *currentSV;
      svRep->setTexCoord(Vec2r((real)u, -1), true);
      i++;
      ++currentSV;
    }
  }

#if 0
  cerr << "u=" << u << "    i=" << i << "/" << _sizeStrip << endl;

  for (i = 0; i < _sizeStrip; i++) {
    _alpha[i] = 1.0;
  }

  for (i = 0; i < _sizeStrip; i++) {
    cerr << "(" << _texCoord[i][0] << ", " << _texCoord[i][1] << ") ";
  }
  cerr << endl;

  Vec2r vec_tmp;
  for (i = 0; i < _sizeStrip / 2; i++) {
    vec_tmp = _vertex[2 * i] - _vertex[2 * i + 1];
  }
  if (vec_tmp.norm() > 4 * _averageThickness) {
    cerr << "Warning (from Fredo): There is a pb in the texture coordinates computation" << endl;
  }
#endif
}

//
// StrokeRep
/////////////////////////////////////

StrokeRep::StrokeRep()
{
  _stroke = 0;
  _strokeType = Stroke::OPAQUE_MEDIUM;
  _nodeTree = NULL;
  _hasTex = false;
  _textureStep = 1.0;
  for (int a = 0; a < MAX_MTEX; a++) {
    _mtex[a] = NULL;
  }
  TextureManager *ptm = TextureManager::getInstance();
  if (ptm) {
    _textureId = ptm->getDefaultTextureId();
  }
#if 0
  _averageTextureAlpha = 0.5;  //default value
  if (_strokeType == OIL_STROKE) {
    _averageTextureAlpha = 0.75;
  }
  if (_strokeType >= NO_BLEND_STROKE) {
    _averageTextureAlpha = 1.0;
  }
#endif
}

StrokeRep::StrokeRep(Stroke *iStroke)
{
  _stroke = iStroke;
  _strokeType = iStroke->getMediumType();
  _nodeTree = iStroke->getNodeTree();
  _hasTex = iStroke->hasTex();
  _textureId = iStroke->getTextureId();
  _textureStep = iStroke->getTextureStep();
  for (int a = 0; a < MAX_MTEX; a++) {
    if (iStroke->getMTex(a)) {
      _mtex[a] = iStroke->getMTex(a);
    }
    else {
      _mtex[a] = NULL;
    }
  }
  if (_textureId == 0) {
    TextureManager *ptm = TextureManager::getInstance();
    if (ptm) {
      _textureId = ptm->getDefaultTextureId();
    }
  }

#if 0
  _averageTextureAlpha = 0.5;  //default value
  if (_strokeType == OIL_STROKE) {
    _averageTextureAlpha = 0.75;
  }
  if (_strokeType >= NO_BLEND_STROKE) {
    _averageTextureAlpha = 1.0;
  }
#endif
  create();
}

StrokeRep::StrokeRep(const StrokeRep &iBrother)
{
  // soc unused - int i = 0;
  _stroke = iBrother._stroke;
  _strokeType = iBrother._strokeType;
  _textureId = iBrother._textureId;
  _textureStep = iBrother._textureStep;
  _nodeTree = iBrother._nodeTree;
  _hasTex = iBrother._hasTex;
  for (int a = 0; a < MAX_MTEX; a++) {
    if (iBrother._mtex[a]) {
      _mtex[a] = iBrother._mtex[a];
    }
    else {
      _mtex[a] = NULL;
    }
  }
  for (vector<Strip *>::const_iterator s = iBrother._strips.begin(), send = iBrother._strips.end();
       s != send;
       ++s) {
    _strips.push_back(new Strip(**s));
  }
}

StrokeRep::~StrokeRep()
{
  if (!_strips.empty()) {
    for (vector<Strip *>::iterator s = _strips.begin(), send = _strips.end(); s != send; ++s) {
      delete (*s);
    }
    _strips.clear();
  }
}

void StrokeRep::create()
{
  vector<StrokeVertex *> strip;
  StrokeInternal::StrokeVertexIterator v = _stroke->strokeVerticesBegin();
  StrokeInternal::StrokeVertexIterator vend = _stroke->strokeVerticesEnd();

  bool first = true;
  bool end = false;
  while (v != vend) {
    while ((v != vend) && (!(*v).attribute().isVisible())) {
      ++v;
      first = false;
    }
    while ((v != vend) && ((*v).attribute().isVisible())) {
      strip.push_back(&(*v));
      ++v;
    }
    if (v != vend) {
      // add the last vertex and create
      strip.push_back(&(*v));
    }
    else {
      end = true;
    }
    if ((!strip.empty()) && (strip.size() > 1)) {
      _strips.push_back(new Strip(strip, _hasTex, first, end, _textureStep));
      strip.clear();
    }
    first = false;
  }
}

void StrokeRep::Render(const StrokeRenderer *iRenderer)
{
  iRenderer->RenderStrokeRep(this);
}

} /* namespace Freestyle */
